package com.pan.insist.filter;

import com.pan.insist.constant.CommonConstant;
import org.jsoup.Jsoup;
import org.jsoup.safety.Whitelist;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * @author kaiji
 */
public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper {

	private static final String SCRIPT_A = "<[\r\n| | ]*script[\r\n| | ]*>(.*?)</[\r\n| | ]*script[\r\n| | ]*>";
	private static final String SCRIPT_B = "src[\r\n| | ]*=[\r\n| | ]*[\\\"|\\\'](.*?)[\\\"|\\\']";
	private static final String SCRIPT_C = "</[\r\n| | ]*script[\r\n| | ]*>";
	private static final String SCRIPT_D = "<[\r\n| | ]*script(.*?)>";
	private static final String SCRIPT_E = "eval\\((.*?)\\)";
	private static final String SCRIPT_F = "e-xpression\\((.*?)\\)";
	private static final String SCRIPT_G = "javascript[\r\n| | ]*:[\r\n| | ]*";
	private static final String SCRIPT_H = "vbscript[\r\n| | ]*:[\r\n| | ]*";
	private static final String SCRIPT_I = "onload(.*?)=";

	/**
	 * 分割符号
	 */
	private static final String SPLIT_FLAG = "##";



	/**
	 * 富文本名称
	 */
	private static final Set<String> RTF_NAMES = Collections.unmodifiableSet(new HashSet<>(
			Arrays.asList("description", "descImage", "chargeImage", "caseDetails")));

	private HttpServletRequest orgRequest;

	XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) {
		super(request);
		orgRequest = request;
	}

	/**
	 * 覆盖getParameter方法，将参数名和参数值都做xss & sql过滤。<br/>
	 * 如果需要获得原始的值，则通过super.getParameterValues(name)来获取<br/>
	 * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
	 */
	@Override
	public String getParameter(String name) {
		String value = super.getParameter(xssEncode(name));
		if (value != null) {
			// 针对富文本，使用Jsoup过滤
			if(isRtf(name)){
				value = Jsoup.clean(value, whiteTagsConfig());
			}
			// 针对其他普通情况，则直接过滤
			else{
				value = xssEncode(value);
			}
		}
		return value;
	}
	
	@Override
    public String[] getParameterValues(String name) {
		String[] values = super.getParameterValues(name);
		if (values != null) {
			int length = values.length;
			String[] result = new String[length];
			for (int i = 0; i < length; i++) {
				if (isRtf(name)) {
					result[i] = Jsoup.clean(values[i], whiteTagsConfig());
				} else {
					result[i] = xssEncode(values[i]);
				}
			}
			return result;
		}
		return super.getParameterValues(name);
    }

	/**
	 * 富文本白名单配置
	 */
	private Whitelist whiteTagsConfig() {
		Whitelist whitelist = Whitelist.basic();
		whitelist.addTags("style");
		whitelist.addAttributes("span", "style");
		whitelist.addAttributes("p", "style");
		whitelist.addAttributes("img", "src", "width", "height");
		whitelist.addTags("video");
		whitelist.addAttributes("video", "src", "width", "height", "autostart", "loop", "controls");
		whitelist.addTags("table");
		whitelist.addAttributes("table", "width", "height", "style", "class");
		whitelist.addTags("tbody");
		whitelist.addTags("tr");
		whitelist.addAttributes("tr", "width", "height", "style", "class");
		whitelist.addTags("td");
		whitelist.addAttributes("td", "width", "height", "style", "class");
		return whitelist;
	}

	@SuppressWarnings("rawtypes")
	@Override
    public Map<String, String[]> getParameterMap() {
		 // 获取当前参数集  
        Map<String, String[]> map = super.getParameterMap();
        Map<String, String[]> result = new HashMap<>(CommonConstant.DEFAULT_INITIAL_CAPACITY);
				for (Map.Entry<String, String[]> stringEntry : map.entrySet()) {
					String key = ((Map.Entry) stringEntry).getKey().toString();
					String[] values = (String[]) ((Map.Entry) stringEntry).getValue();
					if (values != null) {
						int length = values.length;
						String[] array = new String[length];
				for (int i = 0; i < length; i++) {
					array[i] = xssEncode(values[i]);
				}
				result.put(key, array);
			} else {
				result.put(key, null);
			}
		}
		return result;
    }

	/**
	 * 覆盖getHeader方法，将参数名和参数值都做xss & sql过滤。<br/>
	 * 如果需要获得原始的值，则通过super.getHeaders(name)来获取<br/>
	 * getHeaderNames 也可能需要覆盖
	 */
	@Override
	public String getHeader(String name) {

		String value = super.getHeader(xssEncode(name));
		if (value != null) {
			value = xssEncode(value);
		}
		return value;
	}

	/**
	 * 将容易引起xss & sql漏洞的半角字符直接替换成全角字符
	 */
	private static String xssEncode(String s) {
		if (s == null || s.isEmpty()) {
			return s;
		} else {
			s = stripXssAndSql(s);
		}
		StringBuilder sb = new StringBuilder(s.length() + 16);
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);
			switch (c) {
			case '>':
				// 转义大于号
				sb.append("＞");
				break;
			case '<':
				// 转义小于号
				sb.append("＜");
				break;
			case '\'':
				// 转义单引号
				sb.append("＇");
				break;
			case '\"':
				// 转义双引号
				sb.append("＂");
				break;
			case '&':
				// 转义&
				sb.append("＆");
				break;
			case '#':
				// 转义#
				sb.append("＃");
				break;
			default:
				sb.append(c);
				break;
			}
		}
		return sb.toString();
	}

	/**
	 * 获取最原始的request
	 */
	HttpServletRequest getOrgRequest() {
		return orgRequest;
	}

	/**
	 * 
	 * 防止xss跨脚本攻击（替换，根据实际情况调整）
	 */
	private static String stripXssAndSql(String value) {
		if (value != null) {
			Pattern scriptPattern = Pattern.compile(SCRIPT_A, Pattern.CASE_INSENSITIVE);
			value = scriptPattern.matcher(value).replaceAll("");
			// Avoid anything in a
			// src="http://www.yihaomen.com/article/java/..." type of
			// e-xpression
			scriptPattern = Pattern.compile(SCRIPT_B, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
			value = scriptPattern.matcher(value).replaceAll("");
			// Remove any lonesome </script> tag
			scriptPattern = Pattern.compile(SCRIPT_C, Pattern.CASE_INSENSITIVE);
			value = scriptPattern.matcher(value).replaceAll("");
			// Remove any lonesome <script ...> tag
			scriptPattern = Pattern.compile(SCRIPT_D, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
			value = scriptPattern.matcher(value).replaceAll("");
			// Avoid eval(...) expressions
			scriptPattern = Pattern.compile(SCRIPT_E, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
			value = scriptPattern.matcher(value).replaceAll("");
			// Avoid e-xpression(...) expressions
			scriptPattern = Pattern.compile(SCRIPT_F, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
			value = scriptPattern.matcher(value).replaceAll("");
			// Avoid javascript:... expressions
			scriptPattern = Pattern.compile(SCRIPT_G, Pattern.CASE_INSENSITIVE);
			value = scriptPattern.matcher(value).replaceAll("");
			// Avoid vbscript:... expressions
			scriptPattern = Pattern.compile(SCRIPT_H, Pattern.CASE_INSENSITIVE);
			value = scriptPattern.matcher(value).replaceAll("");
			// Avoid onload= expressions
			scriptPattern = Pattern.compile(SCRIPT_I, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
			value = scriptPattern.matcher(value).replaceAll("");
		}
		return value;
	}
	
	// 判断是否为富文本

	/**
	 * 白名单
	 * @param name
	 * 		富文本名称
	 */
	private boolean isRtf(String name){
		boolean flag = true;
		if(RTF_NAMES.contains(name)){
			return true;
		}
		for (String rtfName : RTF_NAMES) {
			if (!rtfName.contains(SPLIT_FLAG)) {
				flag = false;
				break;
			}

			for (String s : rtfName.split(SPLIT_FLAG)){
				if (!name.contains(s)) {
					flag = false;
					break;
				}
			}
			return flag;
		}
		return flag;
	}
}